#include "log.h"
#include "eclr.h"
#include "lwip_socket.h"
#include "bsdsocketdef.h"
#include "sockets.h"

extern "C" int lwip_close(int);

//
// Socket datagram base class
//
LWIPSocket::LWIPSocket() 
{
        
    soHndl   = -1;
}

LWIPSocket::~LWIPSocket() 
{
    
    Close();
}


bool LWIPSocket::isOpen() 
{
    bool result = true;

	
    if (soHndl < 0)
    {
       result = false;
    }
	
	return result;
}

bool LWIPSocket::Open(SocketType mode, int port, int backlog)
{

	int resval;
	
		
    if (isOpen() == true) 
    {
        // already open
        
       
        return false;
    }

    // constructor which creates a socket
    // create a TCP/IP socket stream

    int sMode = 0;
    switch (mode) 
    {
        case Packet :
              sMode = SOCK_DGRAM;
              break;
        case Stream :
              sMode = SOCK_STREAM;
              break;
        default :
              break;  
    }
    
    soHndl = bsdSocket(AF_INET, sMode, 0);
   
debugout("@@@soHndl%d\r\n",soHndl);	
    if (isOpen()) 
    {
                       
        // bind
        struct sockaddr* plAddr       = NULL;
        struct sockaddr_in  lInAddr;
        int    addrLen = 0;
        memset(&lInAddr,0,sizeof(lInAddr));

        lInAddr.sin_family = AF_INET;
        lInAddr.sin_port   = htons((uint16)port);
        plAddr  = (struct sockaddr*)&lInAddr;
        addrLen = sizeof(lInAddr);
        int result = bsdBind(soHndl, plAddr, addrLen);
// debugout("@@@bsdBind :%d\r\n",result);
        if (result == 0)
        {
            if (mode == Stream && port != 0) 
            {
                setAcceptSemantic();
                // passiv open : prepare for accept connections   
                if (backlog == 0) backlog = 4;
                // if needed set the backlog parameter for the accepting queue
                result = bsdListen(soHndl,backlog);
// debugout("@@@bsdListen :%d\r\n",result);

            }

        } 

        if (result != 0) 
        {
            // destroy the connection oriented socket stream
             printf("bind failed\n");
	        Close();
	        
        }
    }        
      
    return isOpen();
}

void LWIPSocket::Close() 
{
    int recvLen;
    
    if (isOpen()) 
    {
       
        		
        lwip_close(soHndl);
       
        soHndl = -1;

    }

}

bool LWIPSocket::Select(SelectMode mode, int timeout_ms)
{
    bool result = false;

    fd_set  rw_set;
    fd_set* pr_set = NULL;
    fd_set* pw_set = NULL;


	FD_ZERO(&rw_set);
    FD_SET(soHndl, &rw_set);

    struct timeval time_out = {0,0};

    // initialize the time structure
    if (timeout_ms >= 1000) 
    {
        time_out.tv_sec    = timeout_ms / 1000;
    }
    else
    {
        time_out.tv_usec   = timeout_ms*1000;
    }

    switch (mode)
    {
        case Receiving:
            pr_set = &rw_set;
            break; 
        case Sending:
            pw_set = &rw_set;
            break; 
    }
      // wait for select
    int  high_fds = soHndl+1;
    
    int r = bsdSelect(high_fds, pr_set, pw_set, NULL, &time_out);
    if (r == 1)
    {
// debugout("@@@bsdSelect :%d\r\n",r);
        result = true;
    }

//////debugout("LWIPSocket::Select:%d %d %d\r\n",time_out.tv_sec,time_out.tv_usec,result);
    return result;
}

bool LWIPSocket::setBroadcast() 
{

    bool result = false;
	
	
    int opt = 1;
    int ret = bsdSetsockopt(soHndl, SOL_SOCKET, SO_BROADCAST, (void*)&opt, sizeof(opt));
// debugout("@@@bsdSetsockopt :%d\r\n",ret);
    if (ret == 0) 
    {
        result = true;
    }

    return result;

}

void LWIPSocket::setNonBlocking()
{
   
// debugout("@@@setNonBlocking :%d\r\n");
    setNonBlockingSemantic();
}

IpAddr LWIPSocket::getSockName() 
{

    IpAddr soName;
    
   
    if (isOpen()) 
    {
        //
        // assign the local ip address of this connection
        //
        struct sockaddr_in lAddr;
        memset(&lAddr,0,sizeof(lAddr));
        lAddr.sin_family  = AF_INET;
        socklen_t lAddrLen = sizeof(lAddr);
        bsdGetsockname(soHndl, (struct sockaddr*)&lAddr, &lAddrLen);
// debugout("@@@bsdGetsockname :%d\r\n",soHndl);

        soName.ipl  = lAddr.sin_addr.s_addr;
        soName.port = ntohs(lAddr.sin_port);

    }

    return(soName);

}

bool LWIPSocket::Connect(const IpAddr& toAddr) {

    bool result = false;
    
  
    if (isOpen()) 
    {
        //
        // Connection to the remote host
        //
        struct sockaddr_in rAddr;
        memset(&rAddr,0,sizeof(rAddr));
        rAddr.sin_addr.s_addr    = toAddr.ipl;
        rAddr.sin_port           = htons((uint16)toAddr.port);
        rAddr.sin_family         = AF_INET;
        if (bsdConnect(soHndl, (struct sockaddr*)&rAddr, sizeof(rAddr)) == 0)
        {
// debugout("@@@bsdConnect :0\r\n");
            // initialize the associated remote endpoint address
            remoteIp = toAddr;

            result = true;
        }
        else
        {
// debugout("@@@bsdConnect :1\r\n");
	        
            Close();
        }
    }

    return result;

}

EclrRemotingSocket* LWIPSocket::Open(BlockingMode mode, bool& wouldblock)
{
    LWIPSocket* result = NULL;
    
    
    if (isOpen()) 
    {
        //
        // accept for data connection
        //
         if (mode == NonBlocking && Select(Receiving, 0) == false)
         {
             return NULL;
         }

// debugout("@@@isOpen :%d\r\n",soHndl);
        struct sockaddr_in rAddr;
        memset(&rAddr,0,sizeof(rAddr));
        rAddr.sin_family = AF_INET;
        socklen_t rAddrLen = sizeof(rAddr);
        int s = bsdAccept(soHndl, (struct sockaddr*)&rAddr, &rAddrLen);

// debugout("@@@bsdAccept :%d\r\n",s);
		
        if (s >= 0) 
        {
            
            result = new LWIPSocket();
            if (result != NULL)
            {
               // *result = *this;

                // dublicate the socket object and assign the new socket handle
                result->soHndl = s;

                // initialize the associated remote endpoint address
                //result->remoteIp.ipl  = rAddr.sin_addr.s_addr;
                //result->remoteIp.port = ntohs(rAddr.sin_port);

                //char ipaddr[32];
                //result->remoteIp.print(ipaddr);

               
            }
        }
        else
        {
	       

        }

    }

    return result;
}

int LWIPSocket::Send(const void* buf, int len)
{

    int sendLen = -1;
    
   

    if (isOpen()) 
    {
#if 0
        if (isNonBlocking() == true && Select(Sending, 0) == false)
        {
            return 0;
        }
#endif
        sendLen = bsdSend(soHndl,(char*)buf, len, 0);
// debugout("@@@sendLen :%d\r\n",sendLen);
    }
    
    
    return(sendLen);

}

int LWIPSocket::Recv(void* buf, int len)
{

    int recvLen = -1;
    
   
    if (isOpen()) 
    {
        // socket state : connected
//debugout("LWIPSocket::Recv @1");             
        if (isNonBlocking() == true)
        {
            // non-blocking mode, check if data are available
//debugout("LWIPSocket::Recv @2");             
            if (Select(Receiving, 0) == true)
            {
                // socket has data
                recvLen = bsdRecv(soHndl,(char*)buf, len, 0);
//debugout("LWIPSocket::Recv len:%d\r\n",recvLen);             
                if (recvLen <= 0)
                {
                    // the socket has received FIN or RST flags, no data
                    // treat like a passive close in blocking-mode
                    recvLen = -1;
                    
                    
                }
            }
            
            else
            {
                // no data available
                recvLen = 0;
            }
        }
        else
        {  
            // blocking mode : the socket controlls the thread execution
            recvLen = bsdRecv(soHndl,(char*)buf, len, 0);
//debugout("LWIPSocket::Recv @3");             
            

        }

    }

    return(recvLen);

}

int LWIPSocket::SendTo(const void* buf, int len, const IpAddr& to) 
{

    int sendLen = -1;
	
    
    if (isOpen()) 
    {

        struct sockaddr_in in;
        memset(&in,0,sizeof(in));
        in.sin_addr.s_addr    = to.ipl;
        in.sin_port   = htons((uint16)to.port);
        in.sin_family = AF_INET;

        sendLen = bsdSendto(soHndl, (char*)buf, len, 0, (struct sockaddr *)&in, sizeof(in));
////debugout("LWIPSocket::SendTo len:%d\r\n",sendLen);             

    }

    return(sendLen);
}

int LWIPSocket::RecvFrom(void* buf, int len, IpAddr& from) 
{

    int recvLen = -1;

	
    if (isOpen()) 
    {
//         if (isNonBlocking() == true && Select(Receiving, 0) == false)
//         {
//             return 0;
//         }

        struct sockaddr_in in;
        memset(&in,0,sizeof(in));
        socklen_t  sockLen  = (socklen_t)sizeof(in);

        recvLen = bsdRecvfrom(soHndl, (char*)buf, len, 0, (struct sockaddr *) &in, &sockLen);
debugout("@@@bsdRecvfrom%d\r\n",recvLen);
        if (recvLen > 0) 
        {
            from.ipl  = in.sin_addr.s_addr;
            from.port = ntohs(in.sin_port);

        }

    }

    return(recvLen);

}


